﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Windows.Globalization;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;

namespace LayoutHelpers.Primitives
{
  public class GoToVisualStateCommand : ICommand
  {
    virtual public string VisualStateName { get; set; }
    virtual public bool UseTransitions { get; set; }

    virtual public bool CanExecute(object parameter)
    {
      return (parameter as Control) != null;
    }
    virtual public void Execute(object parameter)
    {
      var control = GetParameterAsControl(parameter);
      VisualStateManager.GoToState(control, VisualStateName, UseTransitions);
    }
    protected Control GetParameterAsControl(object parameter)
    {
      if (parameter == null)
        throw new ArgumentNullException();

      var control = parameter as Control;
      if (control == null)
        throw new ArgumentOutOfRangeException();
      return control;
    }
   
    protected void RaiseCanExecuteChanged()
    {
      var handler = CanExecuteChanged;
      if (handler != null)
        CanExecuteChanged(this, EventArgs.Empty);
    }
    public event EventHandler CanExecuteChanged;
  }

  [ContentProperty(Name = "Value")]
  public class UpdateResourcePropertyCommand : ICommand
  {
    string name;
    virtual public string Name
    {
      get { return name; }
      set { if (name == value) return; resource = null; name = value; isLastConvertedValueValid = false; }
    }

    string property;
    virtual public string Property
    {
      get { return property; }
      set { if (property == value) return; propertyInfo = null; property = value; isLastConvertedValueValid = false; }
    }

    object value;
    virtual public object Value
    {
      get { return this.value; }
      set { if (this.value == value) return; this.value = value; isLastConvertedValueValid = false; }
    }

    IValueConverter valueConverter;
    virtual public IValueConverter ValueConverter
    {
      get { return valueConverter; }
      set { if (valueConverter == value) return; valueConverter = value; isLastConvertedValueValid = false; }
    }

    object valueConverterParameter;
    virtual public object ValueConverterParameter
    {
      get { return valueConverterParameter; }
      set { if (valueConverterParameter == value) return; valueConverterParameter = value; isLastConvertedValueValid = false; }
    }

    virtual public bool CanExecute(object parameter)
    {
      return (parameter as FrameworkElement) != null;
    }

    virtual public void Execute(object parameter)
    {
      if (parameter != lastParameter)
      {
        element = GetParameterAsFrameworkElement(parameter);
        resource = null;
        propertyInfo = null;
        isLastConvertedValueValid = false;
        lastParameter = parameter;
      }

      if (resource == null)
        resource = element.Resources[Name];

      if (propertyInfo == null && resource != null)
        propertyInfo = resource.GetType().GetTypeInfo().GetDeclaredProperty(Property);

      if (propertyInfo == null)
        return;

      object convertedValue = null;
      if (Value != lastValue)
        isLastConvertedValueValid = false;
      else
        convertedValue = lastConvertedValue;

      bool result = true;
      if (!isLastConvertedValueValid)
        result = TryConvertForProperty(propertyInfo, Value, out convertedValue);

      if (result)
      {
        isLastConvertedValueValid = true;
        lastConvertedValue = convertedValue;
        lastValue = value;
        try
        {
          propertyInfo.SetValue(resource, convertedValue);
        }
        catch (Exception ex)
        {
          Debug.WriteLine("Can't set {0} to value {1}: {2}", propertyInfo.Name, convertedValue, ex.Message);
        }
      }
    }

    private bool TryConvertForProperty(PropertyInfo propertyInfo, object value, out object convertedValue)
    {
      convertedValue = null;
      var type = propertyInfo.PropertyType;
      var typeInfo = type.GetTypeInfo();

      try
      {
        if (valueConverter != null)
          convertedValue = valueConverter.Convert(value, type, valueConverterParameter, ApplicationLanguages.Languages[0]);
        else if (value == null && typeInfo.IsAssignableFrom(typeof(object).GetTypeInfo()))
          convertedValue = null;
        else if (typeInfo.IsAssignableFrom(value.GetType().GetTypeInfo()))
          convertedValue = value;
        else if (typeof(Enum).GetTypeInfo().IsAssignableFrom(typeInfo))
          convertedValue = Enum.Parse(type, value.ToString());
        else
          convertedValue = Convert.ChangeType(value, type);

        return true;
      }
      catch
      {
        Debug.WriteLine("Unsupported type {0}", type.Name);
        return false;
      }
    }

    object lastParameter;
    object resource;
    FrameworkElement element;
    PropertyInfo propertyInfo;
    object lastValue;
    object lastConvertedValue;
    bool isLastConvertedValueValid;

    protected FrameworkElement GetParameterAsFrameworkElement(object parameter)
    {
      if (parameter == null)
        throw new ArgumentNullException();

      var element = parameter as FrameworkElement;
      if (parameter == null)
        throw new ArgumentOutOfRangeException();

      return element;
    }

    protected void RaiseCaneExecuteChanged()
    {
      var handler = CanExecuteChanged;
      if (handler != null)
        CanExecuteChanged(this, EventArgs.Empty);
    }
    public event EventHandler CanExecuteChanged;
  }

  [ContentProperty(Name = "Commands")]
  public class CommandGroup : ICommand
  {
    public List<ICommand> Commands { get; private set; }

    public CommandGroup()
    {
      Commands = new List<ICommand>();
    }

    public bool CanExecute(object parameter)
    {
      return Commands.Any(c => c.CanExecute(parameter));
    }

    public void Execute(object parameter)
    {
      foreach (var c in Commands)
      {
        if (c.CanExecute(parameter))
          c.Execute(parameter);
      }
    }

    protected void RaiseCanExecuteChanged()
    {
      var handler = CanExecuteChanged;
      if (handler != null)
        CanExecuteChanged(this, EventArgs.Empty);
    }
    public event EventHandler CanExecuteChanged;
  }
}